스키마 스티칭으로 GraphQL 페더레이션의 강력한 기능을 활용하세요. 여러 서비스로부터 통합된 GraphQL API를 구축하여 확장성과 유지보수성을 향상시키는 방법을 배워보세요.
GraphQL 페더레이션: 스키마 스티칭 - 종합 가이드
끊임없이 진화하는 현대 애플리케이션 개발 환경에서 확장 가능하고 유지보수하기 쉬운 아키텍처의 필요성이 무엇보다 중요해졌습니다. 마이크로서비스는 내재된 모듈성과 독립적인 배포 가능성 덕분에 인기 있는 해결책으로 부상했습니다. 그러나 수많은 마이크로서비스를 관리하는 것은 특히 클라이언트 애플리케이션에 통합된 API를 노출할 때 복잡성을 야기할 수 있습니다. 바로 이 지점에서 GraphQL 페더레이션, 특히 스키마 스티칭이 중요한 역할을 합니다.
GraphQL 페더레이션이란?
GraphQL 페더레이션은 여러 개의 기본 GraphQL 서비스(주로 마이크로서비스를 나타냄)로부터 단일의 통합된 GraphQL API를 구축할 수 있게 해주는 강력한 아키텍처입니다. 이를 통해 개발자들은 여러 서비스에 걸친 데이터를 마치 하나의 그래프인 것처럼 쿼리할 수 있어, 클라이언트 경험을 단순화하고 클라이언트 측의 복잡한 오케스트레이션 로직의 필요성을 줄여줍니다.
GraphQL 페더레이션에는 두 가지 주요 접근 방식이 있습니다:
- 스키마 스티칭(Schema Stitching): 여러 GraphQL 스키마를 게이트웨이 계층에서 단일의 통합된 스키마로 결합하는 방식입니다. 이는 초기 접근법으로, 스키마 결합 및 쿼리 위임을 관리하기 위해 라이브러리에 의존합니다.
- 아폴로 페더레이션(Apollo Federation): 선언적 스키마 언어와 전용 쿼리 플래너를 사용하여 페더레이션 프로세스를 관리하는 더 최신의 견고한 접근 방식입니다. 타입 확장, 키 지시어, 분산 추적과 같은 고급 기능을 제공합니다.
이 글에서는 스키마 스티칭에 초점을 맞추어 그 개념, 장점, 한계 및 실제 구현 방법을 탐구합니다.
스키마 스티칭 이해하기
스키마 스티칭은 여러 GraphQL 스키마를 하나의 일관된 스키마로 병합하는 과정입니다. 이 통합된 스키마는 클라이언트로부터 기본 서비스의 복잡성을 숨기는 파사드(facade) 역할을 합니다. 클라이언트가 스티칭된 스키마에 요청을 보내면, 게이트웨이는 지능적으로 요청을 적절한 기본 서비스로 라우팅하고, 데이터를 검색한 후, 결과를 결합하여 클라이언트에게 반환합니다.
이렇게 생각해 보세요. 각기 다른 요리를 전문으로 하는 여러 레스토랑(서비스)이 있습니다. 스키마 스티칭은 각 레스토랑의 모든 요리를 결합한 통합 메뉴와 같습니다. 고객(클라이언트)이 이 통합 메뉴에서 주문하면, 주문은 지능적으로 해당 레스토랑의 주방으로 전달되고, 음식이 준비된 후 하나의 배달로 합쳐져 고객에게 전달됩니다.
스키마 스티칭의 핵심 개념
- 원격 스키마(Remote Schemas): 각 기본 서비스의 개별 GraphQL 스키마입니다. 각 서비스는 자신이 제공하는 데이터와 오퍼레이션을 정의하는 자체 스키마를 노출합니다.
- 게이트웨이(Gateway): 원격 스키마들을 함께 스티칭하고 통합된 스키마를 클라이언트에 노출하는 책임을 지는 중앙 구성 요소입니다. 클라이언트 요청을 받아 적절한 서비스로 라우팅하고 결과를 결합합니다.
- 스키마 병합(Schema Merging): 원격 스키마들을 단일 스키마로 결합하는 과정입니다. 이 과정에는 종종 충돌을 피하기 위해 타입과 필드의 이름을 변경하고, 여러 스키마에 걸친 타입 간의 관계를 정의하는 작업이 포함됩니다.
- 쿼리 위임(Query Delegation): 클라이언트가 스티칭된 스키마에 요청을 보낼 때, 게이트웨이는 데이터를 검색하기 위해 요청을 적절한 기본 서비스(들)에 위임해야 합니다. 이는 클라이언트의 쿼리를 원격 서비스가 이해할 수 있는 쿼리로 변환하는 작업을 포함합니다.
- 결과 집계(Result Aggregation): 게이트웨이가 기본 서비스로부터 데이터를 검색한 후, 클라이언트에게 반환할 수 있는 단일 응답으로 결과를 결합해야 합니다. 이는 종종 스티칭된 스키마의 구조에 맞게 데이터를 변환하는 작업을 포함합니다.
스키마 스티칭의 장점
스키마 스티칭은 마이크로서비스 아키텍처를 채택하는 조직에 여러 가지 강력한 이점을 제공합니다:
- 통합 API: 클라이언트를 위한 단일하고 일관된 API를 제공하여 데이터 접근을 단순화하고 클라이언트가 여러 서비스와 직접 상호작용할 필요성을 줄입니다. 이는 더 깔끔하고 직관적인 개발자 경험으로 이어집니다.
- 클라이언트 복잡성 감소: 클라이언트는 통합된 스키마와만 상호작용하면 되므로, 기본 마이크로서비스 아키텍처의 복잡성으로부터 보호받습니다. 이는 클라이언트 측 개발을 단순화하고 클라이언트에 필요한 코드의 양을 줄입니다.
- 확장성 증대: 각 서비스의 특정 요구에 따라 개별적으로 서비스를 확장할 수 있습니다. 이는 시스템의 전반적인 확장성과 복원력을 향상시킵니다. 예를 들어, 높은 부하를 겪는 사용자 서비스는 제품 카탈로그와 같은 다른 서비스에 영향을 주지 않고 확장할 수 있습니다.
- 유지보수성 향상: 모듈성과 관심사의 분리를 촉진하여 개별 서비스를 유지하고 발전시키기 쉽게 만듭니다. 한 서비스의 변경이 다른 서비스에 영향을 미칠 가능성이 줄어듭니다.
- 점진적 도입: 점진적으로 구현할 수 있어 모놀리식 아키텍처에서 마이크로서비스 아키텍처로 점차 이전할 수 있습니다. 기존 API들을 스티칭하는 것부터 시작하여 점차 모놀리스를 더 작은 서비스로 분해할 수 있습니다.
스키마 스티칭의 한계
스키마 스티칭은 수많은 장점을 제공하지만, 그 한계를 인지하는 것이 중요합니다:
- 복잡성: 스키마 스티칭을 구현하고 관리하는 것은 특히 크고 복잡한 시스템에서 복잡할 수 있습니다. 신중한 계획과 설계가 필수적입니다.
- 성능 오버헤드: 게이트웨이는 추가적인 간접 계층과 쿼리를 위임하고 결과를 집계해야 하는 필요성 때문에 약간의 성능 오버헤드를 발생시킵니다. 이 오버헤드를 최소화하기 위해 신중한 최적화가 중요합니다.
- 스키마 충돌: 서로 다른 서비스의 스키마를 병합할 때, 특히 같은 타입 이름이나 필드 이름을 사용하는 경우 충돌이 발생할 수 있습니다. 이를 해결하기 위해서는 신중한 스키마 설계와 잠재적으로 타입 및 필드 이름 변경이 필요합니다.
- 제한적인 고급 기능: 아폴로 페더레이션과 비교할 때, 스키마 스티칭은 타입 확장 및 키 지시어와 같은 일부 고급 기능이 부족하여 여러 스키마에 걸친 타입 간의 관계를 관리하기가 더 어려울 수 있습니다.
- 도구의 성숙도: 스키마 스티칭을 둘러싼 도구와 생태계는 아폴로 페더레이션만큼 성숙하지 않습니다. 이로 인해 문제를 디버깅하고 해결하는 것이 더 어려울 수 있습니다.
스키마 스티칭의 실제 구현
Node.js와 `graphql-tools` 라이브러리(스키마 스티칭에 널리 사용되는 선택지)를 사용하여 스키마 스티칭을 구현하는 간단한 예제를 살펴보겠습니다. 이 예제는 사용자 서비스와 제품 서비스라는 두 개의 마이크로서비스를 포함합니다.
1. 원격 스키마 정의하기
먼저, 각 원격 서비스에 대한 GraphQL 스키마를 정의합니다.
사용자 서비스 (user-service.js
):
const { buildSchema } = require('graphql');
const userSchema = buildSchema(`
type User {
id: ID!
name: String
email: String
}
type Query {
user(id: ID!): User
}
`);
const users = [
{ id: '1', name: 'Alice Smith', email: 'alice@example.com' },
{ id: '2', name: 'Bob Johnson', email: 'bob@example.com' },
];
const userRoot = {
user: (args) => users.find(user => user.id === args.id),
};
module.exports = {
schema: userSchema,
rootValue: userRoot,
};
제품 서비스 (product-service.js
):
const { buildSchema } = require('graphql');
const productSchema = buildSchema(`
type Product {
id: ID!
name: String
price: Float
userId: ID! # 사용자 서비스에 대한 외래 키
}
type Query {
product(id: ID!): Product
}
`);
const products = [
{ id: '101', name: 'Laptop', price: 1200, userId: '1' },
{ id: '102', name: 'Smartphone', price: 800, userId: '2' },
];
const productRoot = {
product: (args) => products.find(product => product.id === args.id),
};
module.exports = {
schema: productSchema,
rootValue: productRoot,
};
2. 게이트웨이 서비스 생성하기
이제 두 스키마를 함께 스티칭할 게이트웨이 서비스를 생성합니다.
게이트웨이 서비스 (gateway.js
):
const { stitchSchemas } = require('@graphql-tools/stitch');
const { makeRemoteExecutableSchema } = require('@graphql-tools/wrap');
const { graphqlHTTP } = require('express-graphql');
const express = require('express');
const { introspectSchema } = require('@graphql-tools/wrap');
const { printSchema } = require('graphql');
const fetch = require('node-fetch');
async function createRemoteSchema(uri) {
const fetcher = async (params) => {
const response = await fetch(uri, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
return response.json();
};
const schema = await introspectSchema(fetcher);
return makeRemoteExecutableSchema({
schema,
fetcher,
});
}
async function main() {
const userSchema = await createRemoteSchema('http://localhost:4001/graphql');
const productSchema = await createRemoteSchema('http://localhost:4002/graphql');
const stitchedSchema = stitchSchemas({
subschemas: [
{ schema: userSchema },
{ schema: productSchema },
],
typeDefs: `
extend type Product {
user: User
}
`,
resolvers: {
Product: {
user: {
selectionSet: `{ userId }`,
resolve(product, args, context, info) {
return info.mergeInfo.delegateToSchema({
schema: userSchema,
operation: 'query',
fieldName: 'user',
args: {
id: product.userId,
},
context,
info,
});
},
},
},
},
});
const app = express();
app.use('/graphql', graphqlHTTP({
schema: stitchedSchema,
graphiql: true,
}));
app.listen(4000, () => console.log('게이트웨이 서버가 http://localhost:4000/graphql 에서 실행 중입니다'));
}
main().catch(console.error);
3. 서비스 실행하기
사용자 서비스와 제품 서비스를 서로 다른 포트에서 실행해야 합니다. 예를 들면 다음과 같습니다:
사용자 서비스 (포트 4001):
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, rootValue } = require('./user-service');
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: rootValue,
graphiql: true,
}));
app.listen(4001, () => console.log('사용자 서비스가 http://localhost:4001/graphql 에서 실행 중입니다'));
제품 서비스 (포트 4002):
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, rootValue } = require('./product-service');
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: rootValue,
graphiql: true,
}));
app.listen(4002, () => console.log('제품 서비스가 http://localhost:4002/graphql 에서 실행 중입니다'));
4. 스티칭된 스키마에 쿼리하기
이제 게이트웨이(포트 4000에서 실행 중)를 통해 스티칭된 스키마에 쿼리할 수 있습니다. 다음과 같은 쿼리를 실행할 수 있습니다:
query {
product(id: "101") {
id
name
price
user {
id
name
email
}
}
}
이 쿼리는 ID가 "101"인 제품을 검색하고, 사용자 서비스에서 연관된 사용자 정보도 가져옵니다. 이는 스키마 스티칭을 통해 단일 요청으로 여러 서비스에 걸쳐 데이터를 쿼리할 수 있음을 보여줍니다.
고급 스키마 스티칭 기법
기본적인 예제를 넘어, 스키마 스티칭 구현을 향상시키는 데 사용할 수 있는 몇 가지 고급 기법은 다음과 같습니다:
- 스키마 위임(Schema Delegation): 요청되는 데이터에 따라 쿼리의 일부를 다른 서비스에 위임할 수 있습니다. 예를 들어, `User` 타입의 리졸루션을 사용자 서비스에, `Product` 타입의 리졸루션을 제품 서비스에 위임할 수 있습니다.
- 스키마 변환(Schema Transformation): 원격 서비스의 스키마를 통합 스키마에 스티칭하기 전에 수정하는 것입니다. 이는 타입 및 필드 이름 변경, 새 필드 추가 또는 기존 필드 제거에 유용할 수 있습니다.
- 커스텀 리졸버(Custom Resolvers): 복잡한 데이터 변환을 처리하거나 여러 서비스에서 데이터를 가져와 단일 결과로 결합하기 위해 게이트웨이에서 커스텀 리졸버를 정의할 수 있습니다.
- 컨텍스트 공유(Context Sharing): 인증 토큰이나 사용자 ID와 같은 컨텍스트 정보를 게이트웨이와 원격 서비스 간에 공유해야 하는 경우가 많습니다. 이는 쿼리 위임 과정의 일부로 컨텍스트 정보를 전달함으로써 달성할 수 있습니다.
- 오류 처리(Error Handling): 원격 서비스에서 발생하는 오류를 정상적으로 처리하기 위해 견고한 오류 처리 메커니즘을 구현합니다. 이는 오류 로깅, 사용자 친화적인 오류 메시지 반환 또는 실패한 요청 재시도를 포함할 수 있습니다.
스키마 스티칭과 아폴로 페더레이션 중 선택하기
스키마 스티칭은 GraphQL 페더레이션을 위한 실행 가능한 옵션이지만, 아폴로 페더레이션은 고급 기능과 향상된 개발자 경험 덕분에 더 인기 있는 선택이 되었습니다. 다음은 두 접근 방식을 비교한 것입니다:
기능 | 스키마 스티칭 | 아폴로 페더레이션 |
---|---|---|
스키마 정의 | 기존 GraphQL 스키마 언어 사용 | 지시어가 포함된 선언적 스키마 언어 사용 |
쿼리 계획 | 수동 쿼리 위임 필요 | 아폴로 게이트웨이에 의한 자동 쿼리 계획 |
타입 확장 | 제한적 지원 | 타입 확장을 위한 내장 지원 |
키 지시어 | 지원 안 됨 | @key 지시어를 사용하여 엔티티 식별 |
분산 추적 | 수동 구현 필요 | 분산 추적을 위한 내장 지원 |
도구 및 생태계 | 덜 성숙한 도구 | 더 성숙한 도구와 대규모 커뮤니티 |
복잡성 | 대규모 시스템에서 관리하기 복잡할 수 있음 | 크고 복잡한 시스템을 위해 설계됨 |
스키마 스티칭을 선택해야 할 때:
- 기존 GraphQL 서비스가 있고 이를 신속하게 결합하고 싶을 때.
- 단순한 페더레이션 솔루션이 필요하고 고급 기능이 필요 없을 때.
- 리소스가 제한적이어서 아폴로 페더레이션 설정의 오버헤드를 피하고 싶을 때.
아폴로 페더레이션을 선택해야 할 때:
- 여러 팀과 서비스가 있는 크고 복잡한 시스템을 구축할 때.
- 타입 확장, 키 지시어, 분산 추적과 같은 고급 기능이 필요할 때.
- 더 견고하고 확장 가능한 페더레이션 솔루션을 원할 때.
- 더 선언적이고 자동화된 페더레이션 접근 방식을 선호할 때.
실제 사례 및 사용 예시
다음은 스키마 스티칭을 포함한 GraphQL 페더레이션이 어떻게 사용될 수 있는지에 대한 실제 예시입니다:
- 전자상거래 플랫폼: 전자상거래 플랫폼은 제품 카탈로그 서비스, 사용자 서비스, 주문 서비스, 결제 서비스 등 여러 서비스의 데이터를 결합하기 위해 GraphQL 페더레이션을 사용할 수 있습니다. 이를 통해 클라이언트는 제품 상세 정보, 사용자 프로필, 주문 내역, 결제 정보를 표시하는 데 필요한 모든 정보를 쉽게 검색할 수 있습니다.
- 소셜 미디어 플랫폼: 소셜 미디어 플랫폼은 사용자 프로필, 게시물, 댓글, '좋아요'를 관리하는 서비스의 데이터를 결합하기 위해 GraphQL 페더레이션을 사용할 수 있습니다. 이를 통해 클라이언트는 사용자의 프로필, 게시물, 그리고 해당 게시물과 관련된 댓글과 '좋아요'를 표시하는 데 필요한 모든 정보를 효율적으로 가져올 수 있습니다.
- 금융 서비스 애플리케이션: 금융 서비스 애플리케이션은 계좌, 거래, 투자를 관리하는 서비스의 데이터를 결합하기 위해 GraphQL 페더레이션을 사용할 수 있습니다. 이를 통해 클라이언트는 계좌 잔액, 거래 내역, 투자 포트폴리오를 표시하는 데 필요한 모든 정보를 쉽게 검색할 수 있습니다.
- 콘텐츠 관리 시스템(CMS): CMS는 기사, 이미지, 비디오, 사용자 생성 콘텐츠와 같은 다양한 소스의 데이터를 통합하기 위해 GraphQL 페더레이션을 활용할 수 있습니다. 이를 통해 특정 주제나 저자와 관련된 모든 콘텐츠를 가져오는 통합 API를 제공할 수 있습니다.
- 헬스케어 애플리케이션: 전자 건강 기록(EHR), 실험실 결과, 예약 스케줄링과 같은 다양한 시스템의 환자 데이터를 통합합니다. 이는 의사에게 포괄적인 환자 정보에 대한 단일 접근점을 제공합니다.
스키마 스티칭을 위한 모범 사례
성공적인 스키마 스티칭 구현을 위해 다음 모범 사례를 따르십시오:
- 스키마를 신중하게 계획하기: 스키마를 함께 스티칭하기 전에 통합 스키마의 구조를 신중하게 계획하십시오. 여기에는 여러 스키마에 걸친 타입 간의 관계 정의, 충돌을 피하기 위한 타입 및 필드 이름 변경, 전반적인 데이터 접근 패턴 고려가 포함됩니다.
- 일관된 명명 규칙 사용하기: 모든 서비스에 걸쳐 타입, 필드, 오퍼레이션에 대한 일관된 명명 규칙을 채택하십시오. 이는 충돌을 피하고 통합 스키마를 더 쉽게 이해하는 데 도움이 됩니다.
- 스키마 문서화하기: 타입, 필드, 오퍼레이션에 대한 설명을 포함하여 통합 스키마를 철저히 문서화하십시오. 이는 개발자들이 스키마를 이해하고 사용하는 것을 더 쉽게 만듭니다.
- 성능 모니터링하기: 게이트웨이와 원격 서비스의 성능을 모니터링하여 성능 병목 현상을 식별하고 해결하십시오. 분산 추적과 같은 도구를 사용하여 여러 서비스에 걸친 요청을 추적하십시오.
- 보안 구현하기: 게이트웨이와 원격 서비스를 무단 접근으로부터 보호하기 위해 적절한 보안 조치를 구현하십시오. 여기에는 인증 및 인가 메커니즘 사용, 입력 유효성 검사 및 출력 인코딩이 포함될 수 있습니다.
- 스키마 버전 관리하기: 스키마를 발전시키면서 적절하게 버전을 관리하여 클라이언트가 이전 버전의 스키마를 문제없이 계속 사용할 수 있도록 하십시오. 이는 호환성이 깨지는 변경을 피하고 하위 호환성을 보장하는 데 도움이 됩니다.
- 배포 자동화하기: 게이트웨이와 원격 서비스의 배포를 자동화하여 변경 사항이 빠르고 안정적으로 배포될 수 있도록 하십시오. 이는 오류 위험을 줄이고 시스템의 전반적인 민첩성을 향상시키는 데 도움이 됩니다.
결론
스키마 스티칭을 이용한 GraphQL 페더레이션은 마이크로서비스 아키텍처에서 여러 서비스로부터 통합된 API를 구축하는 강력한 접근법을 제공합니다. 핵심 개념, 장점, 한계 및 구현 기법을 이해함으로써 스키마 스티칭을 활용하여 데이터 접근을 단순화하고 확장성을 향상시키며 유지보수성을 높일 수 있습니다. 아폴로 페더레이션이 더 진보된 솔루션으로 부상했지만, 스키마 스티칭은 더 간단한 시나리오나 기존 GraphQL 서비스를 통합할 때 여전히 실행 가능한 옵션입니다. 조직의 특정 요구 사항과 필요를 신중하게 고려하여 최상의 접근 방식을 선택하십시오.